;;; al-package.el --- Additional functionality for Emacs package system

;; Copyright © 2014-2016 Alex Kost

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.

;;; Code:

(require 'package)
(require 'al-quelpa)

(defun al/package-installed-p (fun package &rest args)
  "Do not check the version of a built-in package.
Some built-in packages (e.g., `org', `erc') do not have 'Version'
header field.  This may break things if a third-party package
relies on a particular version of a built-in package (e.g.,
'org-6.1' or 'erc-5.3').  So just ignore the version.

This function is intendend to be used as an 'around' advice for
`package-installed-p'."
  (or (package-built-in-p package)
      (apply fun package args)))


;;; Ignoring packages

;; Emacs packages have dependencies (written in "Package-Requires"
;; header of elisp files), and there is no easy way to ignore these
;; dependencies.  I use the code below to install only those packages
;; that I want and to ignore packages from `al/ignored-packages' even if
;; they are required by the other ones.

(defvar al/ignored-packages nil
  "List of names (symbols) of packages that shouldn't be installed.")

(defun al/remove-ignored-packages (requirements)
  "Remove `al/ignored-packages' from the REQUIREMENTS."
  (cl-remove-if (lambda (req)
                  (memq (car req) al/ignored-packages))
                requirements))

;; XXX In a better world, advising `package-desc-reqs' would work, but
;; it doesn't, presumably because it is `cl-defsubst' generated by
;; (cl-defstruct (package-desc ...)).  Is there a workaround?

;; (defun al/package-desc-reqs (fun desc &rest args)
;;   "Return requirements without `al/ignored-packages'."
;;   (let ((reqs (apply fun desc args)))
;;     (al/remove-ignored-packages reqs)))

;; (advice-add 'package-desc-reqs :around #'al/package-desc-reqs)

;; Since the above won't work, I have to mess with modifying other
;; functions:
;;
;; - `quelpa-package-install' and `package-compute-transaction': to
;; avoid building/installing unneeded dependencies;
;;
;; - `package-activate-1': to ignore unneeded dependencies from a
;; generated "…-pkg.el" file, thus to make sure startup activation will
;; not complain about missing packages.  Obviously this function should
;; be advised before `package-initialize' is called.

(defun al/quelpa-package-install (fun package &rest args)
  "Do not install PACKAGE if it is one of `al/ignored-packages'.
This function is intendend to be used as an 'around' advice for
`quelpa-package-install'."
  (let* ((name (al/package-name package))
         (ignore? (memq name al/ignored-packages)))
    (if ignore?
        (message "Ignoring '%s' package." name)
      (apply fun package args))))

(defun al/package-compute-transaction (fun packages requirements
                                           &rest args)
  "Reduce REQUIREMENTS by excluding `al/ignored-packages'.
This function is intendend to be used as an 'around' advice for
`package-compute-transaction'."
  (apply fun packages
         (al/remove-ignored-packages requirements)
         args))

(defun al/package-activate-1 (fun pkg-desc &rest args)
  "Reduce requirements from PKG-DESC by excluding `al/ignored-packages'.
This function is intendend to be used as an 'around' advice for
`package-activate-1'."
  (setf (package-desc-reqs pkg-desc)
        (al/remove-ignored-packages (package-desc-reqs pkg-desc)))
  (apply fun pkg-desc args))

(provide 'al-package)

;;; al-package.el ends here
